"""

 IMAP4 ACL support

 $Id: imap4acl.py 458 2012-12-28 20:27:24Z akelge@gmail.com $

"""
import imaplib
from model import *
# from meta import Session
import meta
import re

ENCODING="hex"

class PublicMailboxes(object):

    def __init__(self,
            user='pubowner@example.com',
            password='xxxxx',
            host='imap',
            connection=None,
            namespace='#Public/'):

        if namespace.endswith('/'):
            self.namespace=namespace
        else:
            self.namespace=namespace+'/'

        self.originalNamespace=self.namespace

        if not connection:
            self.connection=self.imapconnect(host=host, user=user, pwd=password)
        else:
            self.connection=connection

        self.getMboxes()


    def getMboxById(self, id):
        for m in self.mboxes:
            if m.mboxname == id.decode(ENCODING):
                return m
        return None

    def imapconnect(self, host, user, pwd):
        connection=imaplib.IMAP4(host)
        connection.login(user, pwd)
        return connection

    def getMboxes(self):
        self.mboxes=[]
        out, arr=self.connection.list(self.namespace)
        for dummy in arr:
            if dummy:
                mbox=dummy.split('"')[-2]
                self.mboxes.append(PublicMailbox(mbox, self))
        return self.mboxes

    def create(self, mailbox):
        """
        Create new mailbox under our namespace
        """

        mbox="%s" %  mailbox.realname
        out, dat = self.connection.create(mbox)
        if out == 'OK':
            out, dat = self.connection.subscribe(mbox)
            if out != 'OK':
                raise Exception('Error subscribing %s' % mbox)
        else:
            raise Exception('Error creating %s' % mbox)
        self.getMboxes()

    def delete(self, mailbox):
        """
        Delete a mailbox under our namespace
        """
        mbox="%s" %  mailbox.realname
        out, dat = self.connection.delete(mbox)
        if out == 'OK':
            out, dat = self.connection.unsubscribe(mbox)
            if out != 'OK':
                raise Exception('Error subscribing %s' % mbox)
        else:
            raise Exception('Error deleting %s' % mbox)
        self.getMboxes()


    def __repr__(self):
        return r"<%s(namespace='%s') ['%s:%s']" % (self.__class__.__name__,
                self.namespace,
                self.connection.host,
                self.connection.port)


class PublicMailbox(object):

    def __init__(self, mboxname, namespace):
        self.mboxname=mboxname
        self.realname=mboxname.replace(namespace.originalNamespace, 'PubSpace/')
        self.connection=namespace.connection

        self.acl=self.getacl()

    def __repr__(self):
        return r"<%s(mboxname='%s') [host='%s:%s']>" % (self.__class__.__name__,
                self.mboxname,
                self.connection.host,
                self.connection.port)

    @property
    def id(self):
        return self.mboxname.encode(ENCODING).strip()

    def getaceById(self, aceid):
        return [a for a in self.getacl() if str(a.dovecotFormat) == str(aceid.decode(ENCODING))][0]

    def getacl(self):
        acl=[]
        out, (aclstr,)=self.connection.getacl(self.realname)
        if out=='NO':
            pass
            # raise Exception('getacl: %s' % aclstr)
        else:
            parts=[''.join(i) for i in re.findall('(?:^|\s)([^"]+?)(?:\s|$)|"(.*?)"', aclstr)]

            for n in range(1,len(parts),2):
                ace=ACE(who=parts[n], flags=parts[n+1])
                if not ace.unmanaged:
                    acl.append(ace)
        return acl

    def deleteace(self, ace):
        out, (a,)=self.connection.deleteacl(self.realname, ace.who)
        if out=='NO':
            raise Exception('deleteacl: %s' % a)
        self.acl=self.getacl()

    def setace(self, ace):
        self.deleteace(ace)
        out, (a,)=self.connection.setacl(self.realname, ace.who, ace.flags)
        if out=='NO':
            raise Exception('setact: %s' % a)
        self.acl=self.getacl()


class ACE(object):
    """
    From RFC 4314 (http://www.rfc-editor.org/rfc/rfc4314.txt)

    The currently defined standard rights are (note that the list below
    doesn't list all commands that use a particular right):

    l - lookup (mailbox is visible to LIST/LSUB commands, SUBSCRIBE
        mailbox)
    r - read (SELECT the mailbox, perform STATUS)
    s - keep seen/unseen information across sessions (set or clear
        \SEEN flag via STORE, also set \SEEN during APPEND/COPY/
        FETCH BODY[...])
    w - write (set or clear flags other than \SEEN and \DELETED via
        STORE, also set them during APPEND/COPY)
    i - insert (perform APPEND, COPY into mailbox)
    p - post (send mail to submission address for mailbox,
        not enforced by IMAP4 itself)
    k - create mailboxes (CREATE new sub-mailboxes in any
        implementation-defined hierarchy, parent mailbox for the new
        mailbox name in RENAME)
    x - delete mailbox (DELETE mailbox, old mailbox name in RENAME)
    t - delete messages (set or clear \DELETED flag via STORE, set
        \DELETED flag during APPEND/COPY)
    e - perform EXPUNGE and expunge as a part of CLOSE
    a - administer (perform SETACL/DELETEACL/GETACL/LISTRIGHTS)

    """
    FLAGS={'lookup': 'l',        'read': 'r',   'write': 'w', 'seenFlag': 's',
            'deletedeFlag': 't', 'insert': 'i', 'post': 'p',
            'expunge': 'e',      'create': 'k', 'delete': 'x', 'admin': 'a'}

    FLAGS=[
            ('lookup', 'l'),
            ('read', 'r'),
            ('seenFlag', 's'),
            ('write', 'w'),
            ('insert', 'i'),
            ('deletedeFlag', 't'),
            ('expunge', 'e'),
            ('post', 'p'),
            ('create', 'k'),
            ('delete', 'x'),
            ('admin', 'a')
            ]

    DESCRIPTIONS={  'l':'mailbox is visible to LIST/LSUB commands, SUBSCRIBE mailbox',
                    'r':'SELECT the mailbox, perform STATUS',
                    's':'keep seen/unseen information across sessions (set or clear \SEEN flag via STORE)',
                    'w':'set or clear flags other than \SEEN and \DELETED via STORE',
                    'i':'perform APPEND, COPY into mailbox',
                    'p':'send mail to submission address for mailbox, not enforced by IMAP4 itself',
                    'k':'CREATE new sub-mailboxes in any implementation-defined hierarchy',
                    'x':'DELETE mailbox',
                    't':'set or clear \DELETED flag via STORE, set \DELETED flag during APPEND/COPY',
                    'e':'perform EXPUNGE and expunge as a part of CLOSE',
                    'a':'perform SETACL/DELETEACL/GETACL/LISTRIGHTS'}


    TEMPLATES=[
            ('Read Only', 'lrs'),
            ('Read/Write', 'lrwis'),
            ('Read/Write/Delete', 'lrwstie'),
            ('Full Access', 'lrwstiekxa')
            ]

    lookup=False
    read=False
    write=False
    seenFlag=False
    deletedeFlag=False
    insert=False
    post=False
    expunge=False
    create=False
    delete=False
    admin=False

    unmanaged=False

    user=None
    group=None
    realusername=''


    def __init__(self, **kwargs):
        """
        ACE(user=UserObj|group=GroupObj|who='user@domain',
        flags='lkw'|lookup=True, ..., admin=True)

        """
        for k in kwargs.keys():
            if k in self.__class__.__dict__.keys():
                self.__setattr__(k, kwargs[k])

    def flag(self, flag):
        """
        Convert flags from short to long format and viceversa
        """
        if len(flag)>1:
            return [k[1] for k in  self.FLAGS if k[0] == flag][0]
        else:
            return [k[0] for k in self.FLAGS if k[1]==flag][0]

    def get_template(self):
        """
        Lookup for a template that matches our flags
        """
        for template, flags in self.TEMPLATES:
            if ''.join(sorted(self.flags))==''.join(sorted(flags)):
                return template
        return 'Custom'

    def set_template(self, template):
        if template in [t for t,f in self.TEMPLATES]:
            self.flags=[f for t,f in self.TEMPLATES if t==template][0]
        else:
            self.flags=''

    template=property(get_template, set_template)

    # @property
    def get_flags(self):
        string=""
        for label, flag in self.FLAGS:
            if self.__getattribute__(label):
                string += flag
        return string

    # @flags.setter
    def set_flags(self, str):
        for label, flag in self.FLAGS:
            self.__setattr__(label, (flag in str))

    flags=property(get_flags, set_flags)

    # @property
    def get_who(self):
        if self.user:
            return self.user.fullusername
        if self.group:
            return '$'+self.group.fullgroupname
        return "authenticated"

    # @who.setter
    def set_who(self, who):

        self.realusername=who

        if who.startswith('pubowner@'): # Skip public space owner
            self.unmanaged=True
            return

        if not '@' in who:
            return
        else:
            (left, right)=who.split('@')
        domain=meta.Session.query(cmDomain).filter(cmDomain.name==right).first()
        if not domain:
            self.unmanaged=True
            return

        if left.startswith('$'):
            self.group=meta.Session.query(cmGroup).filter(cmGroup.groupname==left.replace('$', '')).filter(cmGroup.domain==domain).first()
            if self.group:
                self.realusername='$%s' % self.group.fullgroupname
            else:
                self.unmanaged=True
                return
        else:
            self.user=meta.Session.query(cmUser).filter(cmUser.username==left).filter(cmUser.domain==domain).first()
            if self.user:
                self.realusername='%s' % self.user.fullusername
            else:
                self.unmanaged=True
                return
    who=property(get_who, set_who)

    @property
    def dovecotFormat(self):
        if self.user:
            return "user=%s\t%s" % (self.user.fullusername, self.flags)
        if self.group:
            return "group=%s\t%s" % (self.group.fullgroupname, self.flags)
        return "authenticated\t%s" % (self.flags)

    @property
    def id(self):
        return self.dovecotFormat.encode(ENCODING).strip()

    def __str__(self):
        if self.user:
            return "%s\t%s" % (self.user.fullusername, self.flags)
        elif self.group:
            return "$%s\t%s" % (self.group.fullgroupname, self.flags)
        else:
            return "authenticated\t%s" % (self.flags)

    def __repr__(self):
        return r"<%s(user='%s', group='%s', flags='%s' [%s])>" % (self.__class__.__name__, self.user,
                self.group, self.flags,
                self.realusername)

